Amazon BedrockのClaudeとAmazon Connectを利用し、電話で色々な質問に答えてくれるコールセンター向けAIチャットボットを構築してみた
はじめに
Amazon BedrockのClaudeとAmazon Connectを利用した、電話で様々な質問に対する応答が可能なコールセンター向けAIチャットボットを構築しました。
電話をかけて質問すると、BedrockのAPIを利用し、質問に答えてくれます。音声は、Amazon Connectが提供するものを活用しています。
以下は、電話をかけた際の対話の様子を示したイメージです:
実際に電話をかけたときのデモ動画です。Bedrockのモデルは、Claude Instant v1.2
を使用しています。
構成図は、以下になります
Connectのコンタクトフロー内で、Lexで質問内容を受け取り、音声から文字起こしされ(裏でAmazon Transcribeが利用)、Lambdaが文字起こしされた質問テキストをBedrock APIにリクエストします。
レスポンス内容をLexに渡し、質問内容に回答する流れです。
前提条件
すべてのリソースは、バージニアリージョンで作成しました。
理由は、Amazon Bedrockで日本語対応のモデルであるClaude v2
とClaude Instant v1.2
をそれぞれ試すためです。
東京リージョンでは、執筆時点でClaude Instant v1.2
しか利用できません。
ただし、両者を試してみると、レスポンス時間が短いClaude Instant v1.2
を採用したため、結果的には東京リージョンでも同様の構築が可能です。
LambdaからBedrock APIにリクエストしてからレスポンスまでの時間は、Claude Instant v1.2
が5秒程度で、Claude v2
が15秒程度かかりました。
- Amazon Bedrockの
Claude Instant
が利用できること - Connectインスタンスをバージニアリージョンで構築済み
- 電話番号も取得済み
構築
以下の流れで構築します。
- Lambdaを作成
- Lexを構築
- Connectのコンタクトフローを作成
Lambdaの構築
Lambdaを作成します。以下を参考に、IAMロールやBedrockを利用するためのBoto3ライブラリをアップロードしてください。
上記に加え、以下の設定をしました。
- IAMロールには、CloudWatch Logsにログを出力するため、
CloudWatchLogsFullAccess
を割り当ててます。 - 実行時間は、デフォルトの3秒から1分に変更しました。
- コードは、下記になります
import json import boto3 from decimal import Decimal bedrock_runtime = boto3.client('bedrock-runtime') def decimal_to_int(obj): if isinstance(obj, Decimal): return int(obj) def elicit_slot(slot_to_elicit, intent_name, slots): return { 'sessionState': { 'dialogAction': { 'type': 'ElicitSlot', 'slotToElicit': slot_to_elicit, }, 'intent': { 'name': intent_name, 'slots': slots, 'state': 'InProgress' } } } def confirm_intent(message_content, intent_name, slots): return { 'messages': [{'contentType': 'PlainText', 'content': message_content}], 'sessionState': { 'dialogAction': { 'type': 'ConfirmIntent', }, 'intent': { 'name': intent_name, 'slots': slots, 'state': 'Fulfilled' } } } def close(fulfillment_state, message_content, intent_name, slots): return { 'messages': [{'contentType': 'PlainText', 'content': message_content}], "sessionState": { 'dialogAction': { 'type': 'Close', }, 'intent': { 'name': intent_name, 'slots': slots, 'state': fulfillment_state } } } def get_bedrock_response(input_text): prompt = f'\n\nHuman: 200字以内で答えてください。答える際、200字以内と言わなくてよいです。{input_text}\n\nAssistant:' modelId = 'anthropic.claude-instant-v1' accept = 'application/json' contentType = 'application/json' body = json.dumps({ "prompt": prompt, "max_tokens_to_sample": 1000 }) response = bedrock_runtime.invoke_model( modelId=modelId, accept=accept, contentType=contentType, body=body ) response_body = json.loads(response.get('body').read()) return response_body.get('completion') def Bedrock_intent(event): print("Received event:" + json.dumps(event, default=decimal_to_int, ensure_ascii=False)) intent_name = event['sessionState']['intent']['name'] slots = event['sessionState']['intent']['slots'] input_text = event['inputTranscript'] if slots['freeinput'] is None: return elicit_slot('freeinput', intent_name, slots) confirmation_status = event['sessionState']['intent']['confirmationState'] if confirmation_status == "Confirmed": return close("Fulfilled", 'それでは、電話を切ります', intent_name, slots) elif confirmation_status == "Denied": return close("Failed", 'お力になれず、申し訳ありません。電話を切ります', intent_name, slots) # confirmation_status == "None" response_text = get_bedrock_response(input_text) print("Received response_text:" + response_text) return confirm_intent( f'それでは、回答します。{response_text}。以上が回答になります。回答に納得したかたは、はい、とお伝え下さい。納得いかない場合、いいえ、とお伝え下さい', intent_name, slots) def lambda_handler(event, context): print("Received event:" + json.dumps(event, default=decimal_to_int, ensure_ascii=False)) intent_name = event['sessionState']['intent']['name'] if intent_name == 'bedrock': return Bedrock_intent(event)
- 関数名:
elicit_slot
- Lexのスロットの値が埋まっていない場合に使用します。
- 関数名:
confirm_intent
- Lexのスロットが全て埋まった時に使用します。
- 確認プロンプトに設定しているプロンプトを伝えます。今回の場合、「それでは、回答します。.......」になります
- Lexのスロットが全て埋まった時に使用します。
- 関数名:
close
- 確認プロンプトの後、インテントを終了するときに使用します。
- クローズ時に設定しているプロンプトを聞きます。
- 確認プロンプトの後、インテントを終了するときに使用します。
- 関数名:
get_bedrock_response
- Bedrock APIからのレスポンスを取得します。
if slots['freeinput'] is None:
このうち、freeinput
は、次の章で説明するLexのスロット名です。if intent_name == 'bedrock':
このうち、bedrock
は、次の章で説明するLexのインテント名です。
Lexを構築
Amazon Lexのボットとインテントを作成します。
もちろん、対応言語は日本語です。
先程作成したLambdaをLexから呼び出すため、指定し保存します。
Lambdaの紐付け設定は、分かりにくいのですが、[エイリアス]→対象のエイリアス(TestBotAlias)→[言語:Japanese (Japan)]を順にクリックすると、紐付ける設定画面がでます。
インテント名はbedrock
とし、インテントを呼び出すためのサンプル発話は、はい
にします。
スロット設定は以下です
- スロット名は
freeinput
- スロットタイプは、自由形式の入力を受け付ける
AMAZON.FreeFormInput
- プロンプトは、「ご質問ください」
AMAZON.FreeFormInput
の詳細は、下記をご参照ください。
先程設定したLambdaが利用されるように、初期化と検証に Lambda 関数を使用
にチェックを入れましょう。
この設定で、[インテントを保存]の後、[Build]し、正常に構築されることを確認します。
Connectのコンタクトフローを作成
対象のConnectインスタンスに、先程作成したLexを登録します
フローは、シンプルですが、下記の4つのブロックのみにしました。
音声設定は、Kazuhaにしてます。言語属性を設定
のチェックも忘れずにしましょう。
[顧客の入力を取得する]ブロックでは、読み上げるテキストを入力し、Lexのボット名やエイリアス名、インテント名を記載して保存します。
フロー公開後、電話番号をフローに割り当てば、Connectの設定は、完了です。
Claudeの回答内容とレスポンス時間
以下は、デモ動画で使用したモデルClaude Instant v1.2
に対して、「東京について」の質問を行った際の回答です。
人口が約1300万人と国内最大の都市で、世界有数の大都市の1つです。 東京の街は23特別区から構成されており、それぞれに個性的な街並みや文化が形成されています。 新宿、渋谷、池袋、すきまなど青年文化が発達した繁華街がある一方で、上野、浅草、秋葉原など歴史ある文化施設も豊富に設けられています。 東京タワーをはじめとするユニークなランドマークも東京の魅力の1つ。 経済や政治の中枢機能を有するだけでなく、文化や arteryの中心地としても発展してきた東京です。
所々日本語に違和感や、英語が混じっています。
レスポンス時間は、5秒程度でした。
一方、モデルClaude v2
に対して同様の質問を行った回答は、以下の通りです。
東京は日本の首都で、世界最大の都市の一つです。 東京には皇居やスカイツリー、渋谷、原宿、秋葉原などたくさんの有名な観光地があります。 また、美術館や建築物、寿司やラーメンなどの食文化も豊富です。 東京はビジネスの中心地である一方、ファッションやカルチャーのトレンドも生まれる活気に満ちた街です。 交通網も発達していて、鉄道を使えばどこへでも簡単に行けます。 東京には見どころがたくさんある素晴らしい街です。
日本語に違和感がなく、分かりやすいです。
ただし、レスポンス時間が15秒程度かかりました。
Claude Instant v1.2
は、レスポンス時間が短い一方、日本語での回答に違和感がありました。
対して、Claude v2
は、日本語の回答は分かりやすいのですが、レスポンス時間が長いというデメリットがありました。つまり、レスポンス時間と回答の精度はトレードオフの関係にあります。
電話での回答と考えると、レスポンス時間が短いClaude Instant v1.2
を選択するしかないかなと思います。
最後に
今回は、Amazon BedrockのClaudeとAmazon Connectを組み合わせて、電話上で質問に応えることが可能なコールセンター向けAIチャットボットを構築しました。
BedrockのClaude Instant v1.2
とClaude v2
について、現時点では、日本語の精度とレスポンス時間はトレードオフですが、今後の性能は向上に期待しましょう。
今後、KendraなどのAIサービスも組み合わせて、様々なテストを試していきたいと思います。